# -*- coding: utf-8 -*-
import os
import json
import time
import hmac
import base64
import hashlib
import random
import requests
import traceback
from datetime import datetime
from email.utils import formatdate
from sinotrans.utils import Logger
from sinotrans.core import FileProcessor
from requests.exceptions import ConnectTimeout, ReadTimeout, ChunkedEncodingError, ConnectionError

class EmailAIService:
    """邮件AI分析服务类 - 抽取AI调用逻辑"""
    
    def __init__(self, max_retries=5,timeout_seconds=30, timeout_dir=None):

        """
        初始化AI服务
        
        Args:
            max_retries (int): 最大重试次数
            timeout_dir (str): 超时文件保存目录
        """
        self.max_retries = max_retries
        self.timeout_seconds = timeout_seconds
        self.timeout_dir = timeout_dir or "./timeout_logs"
        
        # 通义千问API配置
        self.qwen_config = {
            'url': 'https://opensseapi.cmhk.com/CMHK-LMMP-PRD_Qwen3_235B/CMHK-LMMP-PRD/v1/chat/completions',
            'headers': {
                'x-gateway-apikey': 'Bearer MWUwZmI5YzEtOThjOC00ZTNiLWE1ZDItODc3OTkyYzM0MTlm',
                'Content-Type': 'application/json'
            }
        }
        
        # DeepSeek API配置
        self.deepseek_config = {
            'url': 'https://opensseapi.cmhk.com/CMHK-CMAIS-LMMP-PRD_DeepSeek_R1/CMHK-CMAIS-LMMP-PRD/v1/chat/completions',
            'access_key': 'KHja6Le6',
            'secret_key': 'F0CB69F423F71855F5DEA235DBAD7495'
        }
        
        # 船公司代码表
        self.shipping_company_table = """
        代码|名称;YMLU|阳明海运、YM;APLU|美国总统轮船;ONEY|海洋网联、ONE;COSU|中远集运、COSCO;CMDU|法国达飞、CMA;HDMU|韩国现代商船、HMM;HLCU|赫伯罗特、HAPAG-LLOYD;JINX|锦江、SJJ;MSCU|地中海海运、MSC;MAEU|马士基、Maersk、MSK;EGLV|长荣海运、EMC;SUDU|汉堡南美;PILN|太平船务、PIL;ZIMU|以星轮船、ZIM;SITG|海丰国际、SITC;KMTC|高丽海运;HALU|兴亚海运、Heung-A;NSSU|南星海运;SKMC|长锦商船;IAAU|运达船务;SMLM|森罗船务;0145|上海海华轮船有限公司;OOLU|东方海外、OOCL;TSQD|德翔海运;ALIU|亚利安莎;ANLC|澳国航运;BLJU|巴拉基船务;DAAE|法国达贸;EPIR|阿联酋航运;FESO|远东船务;GOSU|金星轮船;HDXU|伊朗船务;HUBU|德利航运;KKLU|川崎汽船;KMBU|神原汽船;MATS|美国美森轮船;MCCQ|穆勒航运;MELX|玛丽亚那;MSGX|民生轮船;NDSU|尼罗河航运;NGPX|太古船务;NODA|诺达纳;NSAU|沙特航运;NYKS|日本邮船;PCLU|泛洲海运;PCSU|东映海运、DY;PUHX|上海浦海;RCLC|泰国宏海;SAFM|南非轮船;SXPG|世腾船务;UASC|阿拉伯轮船;USLU|美国轮船;WHLC|万海航运;CUL|中联航运;Harbour|港业航运;MAERSK SEALAND|海陆-马士基;EUKO|尤科尔、Eukor;EXPRESS|快递公司;POBU|泛洋商船;CRIMT|中铁国际多式联运有限公司;DHL|DHL Express;0146|太仓港集装箱海运有限公司;EUROWAY|EUROWAY、EUROWAY;CNCC|正利航运;ZGLU|中谷海运;BS-IM-SHIPPING|Bosch Intra-asia import shipping company、Bosch Intra-asia import shipping company;ASL|亚海航运;PLVC|PANTOS、Pantos;IAL|INterasia;30079711|宁波远洋运输股份有限公司、NINGBO OCEAN SHIPPING CO.,LTD;CSE|中国船舶集团;KHNN|K+N;HEDE|合德（香港）国际航运有限公司、HEDE (HONGKONG) INTERNATIONAL SHIPPING LIMITED;DACHSER|德莎国际货运代理有限公司;30600441|SINOTRANS VIETNAM LOGISTICS COMPANY LIMITED;蓝水航运|蓝水航运、BLUE WATER LINES PTE LTD;Tailwind|Tailwind Shipping Lines;APS|億航海运、ALPINE SHIPPING;CSCL|中海集运;MCPU|新加坡海领船务有限公司;11DX|正利航业股份有限公司;0301|深圳爱西恩国际物流有限公司;KWEO|近铁集团;30150445|深圳市运达国际船舶代理有限公司;PCIU|太平船务;0346|唐山港合德海运有限公司;SITC|SITC Container Lines Co., LTD;CRIMT-NN|中铁国际多式联运有限公司南宁分公司、中铁;ANTONG|安通物流;CTP LINE|CTP LINE;Pacific Star|Pacific Star;CKCO|天敬海运###
        """
        
        # 确保超时目录存在
        if not os.path.exists(self.timeout_dir):
            os.makedirs(self.timeout_dir)
    
    def _create_deepseek_headers(self, body_json):
        """创建DeepSeek API请求头"""
        method = "POST"
        
        # 计算请求体的SHA-256哈希
        digest_hash = hashlib.sha256(body_json.encode()).digest()
        digest = "SHA-256=" + base64.b64encode(digest_hash).decode()
        
        # 当前时间（HTTP格式）
        date = formatdate(timeval=None, localtime=False, usegmt=True)
        
        # 创建签名字符串
        request_line = f"{method} /CMHK-CMAIS-LMMP-PRD_DeepSeek_R1/CMHK-CMAIS-LMMP-PRD/v1/chat/completions HTTP/1.1"
        signing_data = f"Digest: {digest}\nX-Date: {date}\nhost: opensseapi.cmhk.com\n{request_line}"
        
        # 使用HMAC-SHA256签名
        signature = hmac.new(
            self.deepseek_config['secret_key'].encode(),
            signing_data.encode(),
            hashlib.sha256
        ).digest()
        
        signature_base64 = base64.b64encode(signature).decode()
        
        return {
            "X-Date": date,
            "Digest": digest,
            "host": "opensseapi.cmhk.com",
            "Authorization": f'hmac username="{self.deepseek_config["access_key"]}", algorithm="hmac-sha256", headers="Digest X-Date host request-line", signature="{signature_base64}"',
            "Content-Type": "application/json"
        }
    
    def _calculate_tokens(self, text, email_subject="", email_wt="", method_name=""):
        """计算token数量并记录日志"""
        try:
            import transformers
            chat_tokenizer_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), "infrastructure")
            tokenizer = transformers.AutoTokenizer.from_pretrained(
                chat_tokenizer_dir, trust_remote_code=True
            )
            tokens = len(tokenizer.encode(text))
            # Logger.debug(f"📥 {email_subject}: {method_name}输入token数: {tokens}")
            
            # 如果token过长，保存到文件
            if tokens > 45000:
                eml_file = os.path.join(self.timeout_dir, f"UID_{email_wt}_{datetime.now().strftime('%Y-%m-%d_%H%M%S')}.txt")
                FileProcessor.save_file_retryable(eml_file, text.encode('utf-8'))
                Logger.debug(f"📥 token过长，已记录{eml_file}处理")
            
            return tokens
        except Exception as e:
            Logger.debug(f"❌ 计算token数量失败: {str(e)}")
            return 0
    
    def _make_api_request(self, url, headers, body_json, api_name="API", email_subject="", email_wt=""):
        """通用API请求方法 - 改进版"""
        # 计算输入token数
        input_tokens = self._calculate_tokens(body_json, email_subject, email_wt, api_name)
        
        for attempt in range(self.max_retries):
            try:
                # 记录开始时间
                start_time = time.time()
                response = requests.post(url, headers=headers, data=body_json, stream=True, timeout=(30, self.timeout_seconds))
                
                if response.status_code == 200:
                    result = ""
                    last_data_time = start_time  # 最后收到数据的时间
                    
                    # 配置超时参数
                    MAX_TOTAL_TIME = self.timeout_seconds * 5  # 总体超时时间
                    MAX_IDLE_TIME = 120  # 最大空闲时间（60秒无数据）
                    
                    for i, line in enumerate(response.iter_lines()):
                        current_time = time.time()
                        elapsed_time = current_time - start_time
                        idle_time = current_time - last_data_time
                        
                        # 检查总体超时
                        if elapsed_time > MAX_TOTAL_TIME:
                            response.close()
                            raise TimeoutError(f"❌ 流式接收总体超时: {elapsed_time:.2f}秒 > {MAX_TOTAL_TIME}秒")
                        
                        if line:
                            # 收到数据，更新最后数据时间
                            last_data_time = current_time
                            
                            decode_line = line.decode('utf-8')[6:]
                            try:
                                response_json = json.loads(decode_line)
                                content = response_json.get("choices", [{}])[0].get("delta", {}).get("content", "")
                                result += content
                            except json.decoder.JSONDecodeError:
                                pass
                        else:
                            # 空行，检查空闲超时——不能简单的认为有行就有有效数据，因为实际传输中会有各种情况下出现的空行
                            if idle_time > MAX_IDLE_TIME:
                                response.close()
                                raise TimeoutError(f"❌ 流式接收空闲超时: {idle_time:.2f}秒无数据 > {MAX_IDLE_TIME}秒")
                
                    # # 记录成功完成的时间
                    # total_time = time.time() - start_time
                    # # Logger.debug(f"✅ 流式接收完成，耗时 {total_time:.2f}秒")
                    
                    # 计算输出token数
                    try:
                        import transformers
                        chat_tokenizer_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), "infrastructure")
                        tokenizer = transformers.AutoTokenizer.from_pretrained(
                            chat_tokenizer_dir, trust_remote_code=True
                        )
                        output_tokens = len(tokenizer.encode(result))
                        total_tokens = input_tokens + output_tokens
                        Logger.debug(f"🧮 总token数: {total_tokens}")
                    except Exception as e:
                        Logger.debug(f"❌ 计算输出token数量失败: {str(e)}")
                    
                    return result
                else:
                    raise RuntimeError(f"❌ Request failed: {response.status_code}, {response.text}")
                    
            except ConnectTimeout as e:
                # 连接超时不重试，直接失败
                raise RuntimeError(f"❌ API请求失败: 连接超时：无法在指定时间内建立连接：{e}")
                
            except ReadTimeout as e:
                # 读取超时不重试，直接失败  
                raise RuntimeError(f"❌ API请求失败: 读取超时：服务器响应时间过长：{e}")
                
            except TimeoutError as e:
                # 我们主动抛出的超时异常，可以重试
                Logger.info(f"⏰ {e} - 正在重试 ({attempt + 1}/{self.max_retries})")
                if attempt < self.max_retries - 1:
                    delay = min(60, 2 ** attempt)
                    Logger.debug(f"等待 {delay} 秒后重试...")
                    time.sleep(delay + random.uniform(0, 1))
                    continue
                else:
                    raise RuntimeError(f"❌ API请求超时失败: {str(e)}") from e
                    
            except (ChunkedEncodingError, ConnectionError) as e:
                # 网络连接问题，可以重试
                Logger.info(f"⏳ 网络连接异常，正在重试 ({attempt + 1}/{self.max_retries}): {str(e)}")
                if attempt < self.max_retries - 1:
                    delay = min(60, 2 ** attempt)
                    Logger.debug(f"等待 {delay} 秒后重试...")
                    time.sleep(delay + random.uniform(0, 1))
                    continue
                else:
                    raise RuntimeError(f"❌ API请求连接失败: {str(e)}") from e
                    
            except Exception as e:
                # 其他未预期的异常，记录详细信息后直接失败
                Logger.error(f"❌ 未预期的API异常: {type(e).__name__}: {str(e)}")
                raise RuntimeError(f"❌ API请求失败: {traceback.format_exc()}") from e
    def extract_email_fields_qwen(self, email_wt, email_subject, email_body, email_sent_date):
        """使用通义千问提取邮件字段信息"""
        prompt = f"""{email_body}是邮件的正文，{email_sent_date}是邮件的发送日期，请根据邮件正文和发送日期，识别出你认为的提货日期或装货日期，将提取的结果以 json 格式输出，键名即为提货日期，必须说出你的思考过程。"""
        
        body = {
            "messages": [{"role": "user", "content": prompt}],
            "stream": True,
            "temperature": 0.6,
            "top_p": 0.95,
            "top_k": 20,
            "min_p": 0.0,
            "max_tokens": 8192
        }
        
        body_json = json.dumps(body)
        return self._make_api_request(
            self.qwen_config['url'], 
            self.qwen_config['headers'], 
            body_json, 
            "通义千问字段提取",
            email_subject,
            email_wt
        )
    
    def extract_shipping_company_qwen(self, email_wt, email_subject, email_body):
        """使用通义千问提取船公司信息"""
        prompt = f"""你是一位精通物流运输专业的专家。我们是中外运公司（Sinotrans），博世（BOSH）是我们的客户，以下内容是我们与博世的往来邮件。
        {email_wt}是业务委托号，以下文本是关于{email_wt}的一封邮件。
        {email_subject}是邮件的标题，{email_body}是邮件的正文，请从标题和正文中准确提取船公司的值。
        以下表格为船公司代码表，"代码"列是船公司的标准代码，"名称"列是该船公司的名称（可能有多个）。
        
        船公司代码表如下：
        （以下数据是一个表格，';'是行的分隔符，'|'是列的分隔符,出现符号'###'说明此段表格数据结束）{self.shipping_company_table}
        
        请注意：
        1.若内容中出现"名称"列的值或"代码"列的值，则将该邮件中的船公司字段的值设为"代码"列的值，若内容中无相关的词，则船公司为空。
        2.有可能内容中出现某个"名称"列的词，如"BOSH"、"BOSCH"，但它是代表其他意思，不代表其船公司是"BS-IM-SHIPPING"，这时候它是一个无效的关键词，这一点你需要自己根据具体场景判断。
        3.仅输出提取后的 json 内容，不输出其他任何信息。若船公司在输入文字中不存在相关信息，则该字段的key需要打印出来，但对应的值为空，不要补充任何其他信息（如"none"）。
        4.若出现多个委托号的信息（多出现在表格），只提取{email_wt}委托号对应的船公司，若{email_wt}没有该信息，则船公司对应的值为空，不要提取其他委托号的信息。
        5.提取内容时，只提取百分百确定正确的内容，若不能确认正确，则该字段值为空。
        6.最多提取一个值，若船公司可能的值有多个（如既存在于邮件标题，又存在于邮件正文），则提取你认为最有可能的值。
        7.邮件标题或邮件正文中的内容，用户可能打错字，比如把"Maersk"打成"Mearsk",对于这种明显的笔误，你需要能发现并把正确的值提取出来。
        8.检查你提取的结果，不要出现"none"等表示空的字符，而是直接把值设为空。
        9.可以从MBL的前四位推断船公司，但一定要符合船公司代码表。
        10.不要用船名去推断船公司的值。
        船名描述：
        1）船名和航次一定会一起出现，举例："KMTC POHANG / 2504S"，其中"KMTC POHANG"为船名，"2504S"为航次。
        2）船名通常由多个单词组成，可能包含地名、公司名或特定的船只名称。
        3）船名通常只由字母组成，如"AS PATRIA"、"OOCL AUSTRALIA"。但也可能包含数字，如"WAN HAI 613"、"WAN HAI 353"。
        4）航次通常以数字开头，且最后一位是字母"W"或"S"，如"2412S"、"24020S"。也可能以字母开头，后面是数字，如"S087"、"W087"。不要从船名去推断船公司的值。"""
        
        body = {
            "messages": [{"role": "user", "content": prompt}],
            "stream": True,
            "temperature": 0.6,
            "top_p": 0.95,
            "top_k": 20,
            "min_p": 0.0,
            "max_tokens": 8192
        }
        
        body_json = json.dumps(body)
        return self._make_api_request(
            self.qwen_config['url'], 
            self.qwen_config['headers'], 
            body_json, 
            "通义千问船公司提取",
            email_subject,
            email_wt
        )
    
    def extract_email_fields_deepseek(self, email_wt, email_subject, email_body, email_sent_date):
        """使用DeepSeek提取邮件字段信息"""
        prompt = f"""你是一位精通物流运输专业的专家。我们是中外运公司（Sinotrans），博世（BOSH）是我们的客户，以下内容是我们与博世的往来邮件。
        {email_wt}是业务委托号，以下文本是关于{email_wt}的一封邮件。
        {email_subject}是邮件的标题，{email_body}是邮件的正文，请从标题和正文中准确提取以下字段的值，其中时间相关的字段可依据"邮件发件日期:{email_sent_date}"来进行判断。
        需提取的字段包括:"船名、航次、ETD、ETA、提货日期、ATD、箱号、封号、MBL、HBL、进仓时间、出库时间、进港时间、ATA、目的港清关完成时间、送货时间"。
        请根据以下字段描述，将提取的结果以 json 格式输出，键名即为上述字段名称。
        
        [字段描述内容与通义千问相同，此处省略以节省空间]
        
        请注意：
        1.仅输出提取后的 json 内容，和你的思考过程。若某字段在输入文字中不存在相关信息，则只输出字段名，该字段对应的值为空，不要补充任何其他信息（如"none"）。
        2.时间字段的输出格式统一为年 - 月 - 日，各类时间相关的字段可以以 "邮件发件日期" 为依据进行推断。
        若下列提取字段"ETD、ETA、提货日期、进仓时间、出库时间、进港时间、ATA、目的港清关完成时间、送货时间"的日期缺少年份，则以"邮件发件日期"的年份进行补充。
        3.忽略邮件中发件人的签名部分（签名通常和其他文字相距较远，通常存在在邮件末尾，可能包含发件人的姓名、职位、联系方式、地址、公司规定（如"务必提供xx"、"从现在开始必须xx"）等信息）
        4.若出现多个委托号的信息（多出现在表格），只提取{email_wt}委托号对应的相关字段，若{email_wt}没有该信息，则该字段对应的值为空，不要提取其他委托号的信息。
        5.提取内容时，只提取百分百确定正确的内容，若不能确认正确，则该字段值为空。
        6.所有字段最多提取一个值，若某个字段可能的值有多个（如某字段既存在于邮件标题，又存在于邮件正文），则优先提取邮件正文的值。
        7.邮件标题或邮件正文中的内容，用户可能打错字，比如把"MBL"打成"MLB",对于这种明显的笔误，你需要能发现并把正确的值提取出来。
        8.检查你提取的结果，不要出现"none"等表示空的字符，而是直接把值设为空。
        9.每个字段的值一定不同，比如船名的值一定与MBL不同，若你提取的两个或多个字段值相同，请保留最确定的字段的值，将其他字段的值设为空。
        10.再次检查你提取的字段值，若原文中没有出现该字段的值，不要自己编造和想象出一个值，而是把该字段值设为空。
        11.请勿将上述字段描述中每行后面的举例内容作为实际的邮件内容，而是严格按照邮件的标题和邮件的正文进行提取。"""
        
        body = {
            "model": "DeepSeek-R1",
            "messages": [{"role": "user", "content": prompt}],
            "stream": True,
            "temperature": 0.01,
            "max_tokens": 6144
        }
        
        body_json = json.dumps(body)
        headers = self._create_deepseek_headers(body_json)
        
        return self._make_api_request(
            self.deepseek_config['url'], 
            headers, 
            body_json, 
            "DeepSeek字段提取",
            email_subject,
            email_wt
        )
    
    def extract_shipping_company_deepseek(self, email_wt, email_subject, email_body):
        """使用DeepSeek提取船公司信息"""
        prompt = f"""你是一位精通物流运输专业的专家。我们是中外运公司（Sinotrans），博世（BOSH）是我们的客户，以下内容是我们与博世的往来邮件。
        {email_wt}是业务委托号，以下文本是关于{email_wt}的一封邮件。
        {email_subject}是邮件的标题，{email_body}是邮件的正文，请从标题和正文中准确提取船公司的值。并输出你的思考过程。
        以下表格为船公司代码表，"代码"列是船公司的标准代码，"名称"列是该船公司的名称（可能有多个）。
        
        船公司代码表如下：
        （以下数据是一个表格，';'是行的分隔符，'|'是列的分隔符,出现符号'###'说明此段表格数据结束）{self.shipping_company_table}
        
        请注意：
        1.仅输出提取后的 json 内容和你的思考过程，不输出其他任何信息。若船公司在输入文字中不存在相关信息，则该字段的key需要打印出来，但对应的值为空，不要补充任何其他信息（如"none"）。
        2.若出现多个委托号的信息（多出现在表格），只提取{email_wt}委托号对应的船公司，若{email_wt}没有该信息，则船公司对应的值为空，不要提取其他委托号的信息。
        3.提取内容时，只提取百分百确定正确的内容，若不能确认正确，则该字段值为空。
        4.最多提取一个值，若船公司可能的值有多个（如既存在于邮件标题，又存在于邮件正文），则提取你认为最有可能的值。
        5.邮件标题或邮件正文中的内容，用户可能打错字，比如把"Maersk"打成"Mearsk",对于这种明显的笔误，你需要能发现并把正确的值提取出来。
        6.检查你提取的结果，不要出现"none"等表示空的字符，而是直接把值设为空。
        7.可以从MBL的前四位推断船公司，但一定要符合船公司代码表。
        8.不要用船名去推断船公司的值。"""
        
        body = {
            "model": "DeepSeek-R1",
            "messages": [{"role": "user", "content": prompt}],
            "stream": True,
            "temperature": 0.01,
            "max_tokens": 6144
        }
        
        body_json = json.dumps(body)
        headers = self._create_deepseek_headers(body_json)
        
        return self._make_api_request(
            self.deepseek_config['url'], 
            headers, 
            body_json, 
            "DeepSeek船公司提取",
            email_subject,
            email_wt
        )
    
    def analyze_email_content(self, email_wt, email_subject, email_body, email_sent_date, use_deepseek=True):
        """分析邮件内容 - 统一入口方法
        
        Args:
            email_wt (str): 委托单号
            email_subject (str): 邮件主题
            email_body (str): 邮件正文
            email_sent_date (str): 邮件发送日期
            use_deepseek (bool): 是否使用DeepSeek模型，默认True
            
        Returns:
            dict: 包含字段提取结果和船公司信息的字典
        """
        try:
            # 提取邮件字段信息
            if use_deepseek:
                fields_result = self.extract_email_fields_deepseek(email_wt, email_subject, email_body, email_sent_date)
                # shipping_result = self.extract_shipping_company_deepseek(email_wt, email_subject, email_body)
            else:
                fields_result = self.extract_email_fields_qwen(email_wt, email_subject, email_body, email_sent_date)
                # shipping_result = self.extract_shipping_company_qwen(email_wt, email_subject, email_body)
            
            # 解析字段提取结果
            try:
                # 处理结果
                start_pos = fields_result.find("json")
                end_pos = fields_result.find("}")
                if start_pos != -1:
                    fields_result = fields_result[start_pos:end_pos + 1]
                fields_result.replace("```", "").replace("json", "").strip()
                # 获取识别结果（仅包含字段）
                fields_data = json.loads(fields_result.strip()) if fields_result else {}
            except json.JSONDecodeError as e:
                Logger.error(f"❌ 字段提取结果JSON解析失败: {e}")
                fields_data = {
                    '船名': '', '航次': '', 'ETD': '', 'ETA': '', '提货日期': '',
                    'MBL': '', 'HBL': '', 'ATD': '', '箱号': '', '封号': '',
                    '进仓时间': '', '出库时间': '', '进港时间': '', 'ATA': '',
                    '目的港清关完成时间': '', '送货时间': ''
                }
            
            # 解析船公司提取结果
            # try:
            #     shipping_data = json.loads(shipping_result.strip()) if shipping_result else {}
            # except json.JSONDecodeError as e:
            #     Logger.error(f"船公司提取结果JSON解析失败: {e}")
            #     shipping_data = {'船公司': ''}
            
            # 合并结果
            result = {
                **fields_data,
                # '船公司': shipping_data.get('船公司', ''),
                '最新识别结果': f"{fields_result}",# \n{shipping_result}
                '最新识别时间': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
            }
            
            return result
            
        except Exception as e:
            Logger.error(f"❌ 邮件内容分析失败: {str(e)}")
            raise
"""
prompt = 你是一位精通物流运输专业的专家。我们是中外运公司（Sinotrans），博世（BOSH）是我们的客户，以下内容是我们与博世的往来邮件。
                    {email_wt}是业务委托号，以下文本是关于{email_wt}的一封邮件。
                    {email_subject}是邮件的标题，{email_body}是邮件的正文，请从标题和正文中准确提取以下字段的值，其中时间相关的字段可依据“邮件发件日期:{email_sent_date}”来进行判断。
                    需提取的字段包括:“船名、航次、ETD、ETA、提货日期、ATD、箱号、封号、MBL、HBL、进仓时间、出库时间、进港时间、ATA、目的港清关完成时间、送货时间”。
                    请根据以下字段描述，将提取的结果以 json 格式输出，键名即为上述字段名称。
                    字段描述：
                    1.船名/航次
                    1）船名和航次一定会一起出现，举例：“KMTC POHANG / 2504S”，其中“KMTC POHANG”为船名，“2504S”为航次。
                    2）若文字中只有船名但没有航次，或只有航次但没有船名，那么船名和航次都设为空。
                    3）船名通常由多个单词组成，可能包含地名、公司名或特定的船只名称。
                    4）船名通常只由字母组成，如“AS PATRIA”、“OOCL AUSTRALIA”。但也可能包含数字，如“WAN HAI 613”、“WAN HAI 353”。
                    5）航次通常以数字开头，且最后一位是字母“W”或“S”，如“2412S”、“24020S”。也可能以字母开头，后面是数字，如“S087”、“W087”。
                    6）提取航次时，要保留原文的后缀，如“S”、“W”等。
                    7）船名也可能以S或s开头，即S或s开头也可视作船名。
                    2.箱号
                    1）箱号严格遵循11位格式：前4位是大写字母，后7位是数字，不满足此格式的一定不是箱号。
                    2）如果一个字段的长度不是11位，或者前4位不是大写字母，或者后7位不是数字，则该字段一定不是箱号。
                    3）在提取箱号时，严格验证格式。即使该字段与“箱号”、“40HC”关键词相邻，只要格式不符合11位规则（前4位大写字母 + 后7位数字），则该字段不应被识别为箱号。
                    4）箱号举例：“FFAU6119411”、“TEMU8525264”。
                    5）一定要注意，箱号严格遵循11位格式，即前4位是大写字母，后7位是数字，不满足此格式的一定不是箱号。
                    3.封号
                    1）通常是一个数字序列，可能包含字母，一般大于五位。
                    2）可能以字母开头，后面跟随数字，如“HASHA41847”、“CNSHK2436365”。
                    3）也可能是纯数字，如“28844855”、“0264887”。
                    4.MBL
                    1）主提单号通常是一个字母和数字的组合，可能包含特定的前缀或后缀。
                    2）前缀可能表示承运人或船公司代码，如“OOLU”、“HASLC”、“COAU”、“KMTC”。
                    3）数字部分通常较长，可能包含10-15位数字。
                    4）MBL举例：“AMIGL240597415A”、“OOLU8898091780”、“KMTCSHAN753208”、“234745738”。
                    5）若MBL前缀或后缀有“BOSH”或“BOSCH”，这时候它是一个无效的关键词，不是MBL。
                    6）当MBL的识别结果为TLX或tlx时，不记录该MBL识别结果。
                    7）当出现以BL或bl为前缀或修饰时，如“BL：”、“bl：”、“B/L：”、“b/l：”等，将其后内容识别为MBL，而非HBL。
                    5.HBL
                    1）通常是字母和数字的组合，格式与MBL类似。
                    2）可能包含特定的前缀或后缀，如“SS”、“STL”、“AMIGL”。“SS”开头的为17位，“STL”开头的为11位。
                    3）数字部分可能较短，通常为8-12位。
                    4）HBL举例：“AMIGL240597415A”、“SS0245BIA24120282”、“STL24082835”。
                    5）当HBL的识别结果为TLX或tlx时，不记录该HBL识别结果。
                    6）当出现以BL或bl为前缀或修饰时，如“BL：”、“bl：”、“B/L：”、“b/l：”等，将其后内容识别为MBL，而非HBL。
                    6.各类时间字段
                    1）如果邮件内容是在询问或预估，没有明确说明xx时间，不要把询问/预估内容当作既定事实。
                    比如“xx时间来提货吗？”、“xx时间可提货”、“货好”（货已经准备好了）、“预计xx日提货”，不要认为这些信息是提货时间的值。
                    请注意：
                    1.仅输出提取后的 json 内容，不输出其他任何信息。若某字段在输入文字中不存在相关信息，则只输出字段名，该字段对应的值为空，不要补充任何其他信息（如“none”）。
                    2.时间字段的输出格式统一为年 - 月 - 日，各类时间相关的字段可以以 “邮件发件日期” 为依据进行推断。
                    若下列提取字段“ETD、ETA、提货日期、进仓时间、出库时间、进港时间、ATA、目的港清关完成时间、送货时间”的日期缺少年份，则以“邮件发件日期”的年份进行补充。
                    3.忽略邮件中发件人的签名部分（签名通常和其他文字相距较远，通常存在在邮件末尾，可能包含发件人的姓名、职位、联系方式、地址、公司规定（如“务必提供xx”、“从现在开始必须xx”）等信息）
                    4.若出现多个委托号的信息（多出现在表格），只提取{email_wt}委托号对应的相关字段，若{email_wt}没有该信息，则该字段对应的值为空，不要提取其他委托号的信息。
                    5.提取内容时，只提取百分百确定正确的内容，若不能确认正确，则该字段值为空。
                    6.所有字段最多提取一个值，若某个字段可能的值有多个（如某字段既存在于邮件标题，又存在于邮件正文），则优先提取邮件正文中的值。
                    7.邮件标题或邮件正文中的内容，用户可能打错字，比如把“MBL”打成“MLB”,对于这种明显的笔误，你需要能发现并把正确的值提取出来。
                    8.检查你提取的结果，不要出现“none”等表示空的字符，而是直接把值设为空。
                    9.每个字段的值一定不同，比如船名的值一定与MBL不同，若你提取的两个或多个字段值相同，请保留最确定的字段的值，将其他字段的值设为空。
                10.再次检查你提取的字段值，若邮件标题或邮件正文中没有出现该字段的值，不要自己编造出一个值作为识别结果，而是把该字段值设为空。
"""